/*
 * Copyright 2012-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.web.servlet;

import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletRegistration;

import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * 一个 {@link ServletContextInitializer}，用于在 Servlet 3.0+ 容器中注册 {@link Servlet}。
 * 类似于 {@link ServletContext} 提供的 {@link ServletContext#addServlet(String, Servlet) 注册}功能，
 * 但设计更适合 Spring Bean 使用。
 * <p>
 * 必须在调用 {@link #onStartup} 之前指定 {@link #setServlet(Servlet) servlet}。
 * URL 映射可通过 {@link #setUrlMappings} 配置，或省略以映射到 '/*'（除非
 * {@link #ServletRegistrationBean(Servlet, boolean, String...) alwaysMapUrl} 设置为 {@code false}）。
 * 如果未指定 servlet 名称，将自动推断。
 *
 * @param <T> 要注册的 {@link Servlet} 类型
 * @author Phillip Webb
 * @since 1.4.0
 * @see ServletContextInitializer
 * @see ServletContext#addServlet(String, Servlet)
 */
public class ServletRegistrationBean<T extends Servlet> extends DynamicRegistrationBean<ServletRegistration.Dynamic> {

	private static final String[] DEFAULT_MAPPINGS = { "/*" };

	private T servlet;

	private Set<String> urlMappings = new LinkedHashSet<>();

	private boolean alwaysMapUrl = true;

	private int loadOnStartup = -1;

	private MultipartConfigElement multipartConfig;

	/**
	 * 创建一个新的 {@link ServletRegistrationBean} 实例。
	 */
	public ServletRegistrationBean() {
	}

	/**
	 * 创建一个新的 {@link ServletRegistrationBean} 实例，并指定 {@link Servlet} 和 URL 映射。
	 * @param servlet 要映射的 servlet
	 * @param urlMappings 要映射的 URL
	 */
	public ServletRegistrationBean(T servlet, String... urlMappings) {
		this(servlet, true, urlMappings);
	}

	/**
	 * 创建一个新的 {@link ServletRegistrationBean} 实例，并指定 {@link Servlet} 实例及其 URL 映射。
	 * @param servlet 要注册的 servlet
	 * @param alwaysMapUrl 是否在未提供映射时使用默认映射 '/*'
	 * @param urlMappings 要映射的 URL
	 */
	public ServletRegistrationBean(T servlet, boolean alwaysMapUrl, String... urlMappings) {
		Assert.notNull(servlet, "Servlet must not be null");
		Assert.notNull(urlMappings, "UrlMappings must not be null");
		this.servlet = servlet;
		this.alwaysMapUrl = alwaysMapUrl;
		this.urlMappings.addAll(Arrays.asList(urlMappings));
	}

	/**
	 * 设置要注册的 servlet。
	 * @param servlet servlet 实例
	 */
	public void setServlet(T servlet) {
		Assert.notNull(servlet, "Servlet must not be null");
		this.servlet = servlet;
	}

	/**
	 * 返回要注册的 servlet。
	 * @return servlet 实例
	 */
	public T getServlet() {
		return this.servlet;
	}

	/**
	 * 设置 servlet 的 URL 映射。如果未指定，则默认映射为 '/'。
	 * 此操作会替换之前设置的所有映射。
	 * @param urlMappings 要设置的映射路径
	 * @see #addUrlMappings(String...)
	 */
	public void setUrlMappings(Collection<String> urlMappings) {
		Assert.notNull(urlMappings, "UrlMappings must not be null");
		this.urlMappings = new LinkedHashSet<>(urlMappings);
	}

	/**
	 * 返回用于该 servlet 的 URL 映射的可变集合，格式符合 Servlet 规范。
	 * @return urlMappings 映射集合
	 */
	public Collection<String> getUrlMappings() {
		return this.urlMappings;
	}

	/**
	 * 为该 servlet 添加 URL 映射，格式符合 Servlet 规范。
	 * @param urlMappings 要添加的映射
	 * @see #setUrlMappings(Collection)
	 */
	public void addUrlMappings(String... urlMappings) {
		Assert.notNull(urlMappings, "UrlMappings must not be null");
		this.urlMappings.addAll(Arrays.asList(urlMappings));
	}

	/**
	 * 设置 {@code loadOnStartup} 优先级。详见 {@link ServletRegistration.Dynamic#setLoadOnStartup}。
	 * @param loadOnStartup 是否启用启动时加载
	 */
	public void setLoadOnStartup(int loadOnStartup) {
		this.loadOnStartup = loadOnStartup;
	}

	/**
	 * 设置 {@link MultipartConfigElement 多部件配置}。
	 * @param multipartConfig 要设置的多部件配置，或 {@code null}
	 */
	public void setMultipartConfig(MultipartConfigElement multipartConfig) {
		this.multipartConfig = multipartConfig;
	}

	/**
	 * 返回要应用的 {@link MultipartConfigElement 多部件配置}，如未设置则返回 {@code null}。
	 * @return 多部件配置
	 */
	public MultipartConfigElement getMultipartConfig() {
		return this.multipartConfig;
	}

	@Override
	protected String getDescription() {
		Assert.notNull(this.servlet, "Servlet must not be null");
		return "servlet " + getServletName();
	}

	@Override
	protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
		String name = getServletName();
		return servletContext.addServlet(name, this.servlet);
	}

	/**
	 * 配置注册设置。子类可以覆盖此方法以根据需要执行其他配置。
	 * @param registration 注册对象
	 */
	@Override
	protected void configure(ServletRegistration.Dynamic registration) {
		super.configure(registration);
		String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
		if (urlMapping.length == 0 && this.alwaysMapUrl) {
			urlMapping = DEFAULT_MAPPINGS;
		}
		if (!ObjectUtils.isEmpty(urlMapping)) {
			registration.addMapping(urlMapping);
		}
		registration.setLoadOnStartup(this.loadOnStartup);
		if (this.multipartConfig != null) {
			registration.setMultipartConfig(this.multipartConfig);
		}
	}

	/**
	 * 返回将被注册的 servlet 名称。
	 * @return servlet 名称
	 */
	public String getServletName() {
		return getOrDeduceName(this.servlet);
	}

	@Override
	public String toString() {
		return getServletName() + " urls=" + getUrlMappings();
	}

}
